// Unit test for functors

#include <iostream>
#include <cstdio>
#include <memory>
#include <cassert>
#include <string>
#include <typeinfo>
#include "fmb.h"

using namespace std;
using namespace fm::base;

class AddImpl {
public:
  double operator()(double t1, double t2) {
    printf("AddImpl::operator()(%.7g, %.7g) was called\n", t1, t2);
    return t1 + t2;
  }
};

double TestAdd(double t1, double t2) {
  printf("TestAdd(%.7g, %.7g) with double was called\n", t1, t2);
  return t1 + t2;
}

double TestAdd(int t1, int t2) {
  printf("TestAdd(%d, %d) with integer parameters was called\n", t1, t2);
  return t1 + t2;
}

class TestClass {
private:
  const char *m_id;
public:

  TestClass(const char *id) : m_id(id) {}

  int foo(int a) { printf("%s::foo(%d)\n", m_id, a); return a; }
  int bar(int a) { printf("%s::bar(%d)\n", m_id, a); return a * 2; }
};

typedef Fn<double, TL_2(double, double)> AddFn;
typedef Fn<double, TL_2(int, int)> IntAddFn;

typedef double (*TestAddFun)(double, double);
typedef double (*TestIntAddFun)(int, int);
typedef Fn<const char *, TL_0> StrFn;

typedef Fn<int, TL_1(int)> ClassFn;

void callbackInvoker(IntAddFn fn) {
  assert(fn(1, 2) == 3);
}

/// <summary>
///   The main function
/// </summary>
int main(int argc, char **argv){

  {
    AddImpl impl;
    AddFn fn1(impl);
    AddFn fn2(fn1);
    AddFn fn3 = fn1;

    assert(fn1(0.5, 0.5) == 1);
    assert(fn2(1.5, 0.5) == 2);
    assert(fn3(2.5, 0.5) == 3);

    IntAddFn intFn1(impl);
    IntAddFn intFn2(intFn1);
    IntAddFn intFn3 = intFn1;
    IntAddFn intFn4(fn1);
    IntAddFn intFn5 = fn1;

    assert(intFn1(1, 1) == 2);
    assert(intFn2(1, 2) == 3);
    assert(intFn3(1, 3) == 4);
    assert(intFn4(1, 4) == 5);
    assert(intFn5(1, 5) == 6);

    printf(" - functor object test done\n");
  }

  {
    AddImpl impl;
    AddFn fn(impl);
    IntAddFn intFn(impl);
    callbackInvoker(fn);
    callbackInvoker(intFn);
    assert(fn(1, 1) == 2);

    printf(" - callback test done\n");
  }

  {
    AddFn fn1(static_cast<TestAddFun>(&TestAdd));
    AddFn fn2(fn1);
    AddFn fn3 = fn1;

    assert(fn1(0.5, 0.5) == 1);
    assert(fn2(1.5, 0.5) == 2);
    assert(fn3(2.5, 0.5) == 3);

    IntAddFn intFn1(static_cast<TestIntAddFun>(&TestAdd));
    IntAddFn intFn2(fn1);
    IntAddFn intFn3 = intFn1;
    IntAddFn intFn4(fn1);
    IntAddFn intFn5 = fn1;

    assert(intFn1(1, 1) == 2);
    assert(intFn2(1, 2) == 3);
    assert(intFn3(1, 3) == 4);
    assert(intFn4(1, 4) == 5);
    assert(intFn5(1, 5) == 6);

    printf(" - function overloading test done\n");
  }

  {
    TestClass tc("Test007");
    ClassFn fn1(&tc, &TestClass::foo);
    ClassFn fn2(&tc, &TestClass::bar);
    ClassFn fn3(fn1);
    ClassFn fn4 = fn1;

    assert(fn1(1) == 1);
    assert(fn2(2) == 4);
    assert(fn3(3) == 3);
    assert(fn4(4) == 4);

    printf(" - object member function test done\n");
  }

  printf("\nThe whole test was successfull\n\n");
  return 0;
}
